package qy.jalgotrade.barfeed;

import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.tuple.Pair;

import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.bar.Bars;
import qy.jalgotrade.dataseries.BarDataSeries;
import qy.jalgotrade.event.Dispatcher;
import qy.jalgotrade.feed.BaseFeed;
import qy.jalgotrade.utils.PySpecs;

/**
 * Base class for :class:`pyalgotrade.bar.Bar` providing feeds.
 * 
 * @author c-geo
 *
 */
public abstract class BaseBarFeed extends BaseFeed<Bar> {

	private final Bar.Frequency __frequency;

	private boolean __useAdjustedValues;

	private String __defaultInstrument;

	private Bars __currentBars;

	private Bars __currentBarsDispatching;

	private Map<String, Bar> __lastBars;
	
	private int __totalTradeDays;

	/**
	 *
	 * @param frequency The bars frequency. Valid values defined in :class:`pyalgotrade.bar.Frequency`.
	 * @throws Exception Exception
	 */
	public BaseBarFeed(Bar.Frequency frequency) throws Exception {

		this(frequency, 0);
	}

	/**
	 * 
	 * @param frequency The bars frequency. Valid values defined in :class:`pyalgotrade.bar.Frequency`.
	 * @param maxLen    The maximum number of values that the
	 *                  :class:`pyalgotrade.dataseries.bards.BarDataSeries` will hold. Once a bounded
	 *                  length is full, when new items are added, a corresponding number of items are
	 *                  discarded from the opposite end. If None then dataseries.DEFAULT_MAX_LEN is
	 *                  used.
	 * @throws Exception Exception
	 */
	public BaseBarFeed(Bar.Frequency frequency, int maxLen) throws Exception {

		super(maxLen);
		__frequency = frequency;
		__useAdjustedValues = false;
		__defaultInstrument = null;
		__currentBars = null;
		__currentBarsDispatching = null;
		__lastBars = new HashMap<>();
		__totalTradeDays = -1;
		setDispatchPriority(Dispatcher.DispatcherPrio.BAR_FEED);
	}

	/**
	 * 
	 * @return __totalTradeDays
	 */
	public int getTotalTradeDays() {

		return __totalTradeDays;
	}

	/**
	 * 
	 * @param tradeDays tradeDays
	 */
	public void setTotalTradeDays(int tradeDays) {

		__totalTradeDays = tradeDays;
	}

	@Override
	public void reset() throws Exception {

		__currentBars = null;
		__currentBarsDispatching = null;
		__lastBars = new HashMap<>();
		super.reset();
	}

	public void setUseAdjustedValues(boolean useAdjusted) throws Exception {

		if (useAdjusted && !barsHaveAdjClose()) {
			throw new Exception("The barfeed doesn't support adjusted close values");
		}
		// This is to affect future dataseries when they get created.
		__useAdjustedValues = useAdjusted;
		// Update existing dataseries
		for (String e : getRegisteredInstruments()) {
			((BarDataSeries) __getitem__(e)).setUseAdjustedValues(useAdjusted);
		}
	}

	/**
	 * Return the datetime for the current bars.
	 * 
	 * @return ZonedDateTime
	 */
	public abstract ZonedDateTime getCurrentDateTime();

	/**
	 * Return True if bars provided have adjusted close values.
	 * 
	 * @return boolean
	 */
	public abstract boolean barsHaveAdjClose();

	/**
	 * <pre>
	 * Subclasses should implement this and return a pyalgotrade.bar.Bars or None if there are no
	 * bars.
	 * 
	 * "Override to return the next :class:`pyalgotrade.bar.Bars` in the feed or None if there are
	 * no bars.
	 * 
	 * .. note::
	 *     This is for BaseBarFeed subclasses and it should not be called directly.
	 * </pre>
	 * 
	 * @return Bars
	 * @throws Exception Exception
	 */
	public abstract Bars getNextBars() throws Exception;

	@Override
	public BarDataSeries createDataSeries(String key, int maxLen) throws Exception {

		BarDataSeries ret = new BarDataSeries(maxLen);
		ret.setUseAdjustedValues(__useAdjustedValues);
		return ret;
	}

	@Override
	public Pair<ZonedDateTime, PySpecs.ReadOnlyMap<String, Bar>> getNextValues() throws Exception {

		ZonedDateTime dateTime = null;
		Bars bars = getNextBars();
		if (bars != null) {
			dateTime = bars.getDateTime();

			// Check that current bar datetimes are greater than the previous one.
			if (__currentBars != null && __currentBars.getDateTime().compareTo(dateTime) >= 0) {
				throw new Exception(String.format(
				        "Bar (frequency: %s) date times are not in order. Previous datetime was %s and current datetime is %s."
				                + "\nprevious bars: %s\ncurrent bars: %s",
				        __frequency, __currentBars.getDateTime(), dateTime, __currentBars, bars));
			}

			// Update self.__currentBars and self.__lastBars
			__currentBarsDispatching = __currentBars;
			__currentBars = bars;
			for (String e : bars.getInstruments()) {
				__lastBars.put(e, bars.__getitem__(e));
			}
		}
		return Pair.of(dateTime, bars);
	}

	public Bar.Frequency getFrequency() {

		return __frequency;
	}

	public boolean isIntraday() {

		return __frequency.getValue() < Bar.Frequency.DAY.getValue();
	}

	/**
	 * Returns the current :class:`pyalgotrade.bar.Bars`.
	 * 
	 * @return Bars
	 */
	public Bars getCurrentBars() {

		if (isDispatching()) {
			return __currentBarsDispatching;
		} else {
			return __currentBars;
		}
	}

	/**
	 * Returns the last :class:`pyalgotrade.bar.Bar` for a given instrument, or None.
	 * 
	 * @param instrument instrument
	 * @return Bar
	 */
	public Bar getLastBar(String instrument) {

		return __lastBars.get(instrument);
	}

	/**
	 * Returns the last instrument registered.
	 * 
	 * @return the last instrument registered
	 */
	public String getDefaultInstrument() {

		return __defaultInstrument;
	}

	/**
	 * 
	 * @param instrument instrument
	 * @throws Exception Exception
	 */
	public void setDefaultInstrument(String instrument) throws Exception {

		if (getKeys().contains(instrument)) {
			__defaultInstrument = instrument;
		} else {
			throw new Exception(String.format("can NOT set unregistered instrument (%s) to default!", instrument));
		}
	}

	/**
	 * Returns a list of registered intstrument names.
	 * 
	 * @return a list of registered intstrument names.
	 */
	public List<String> getRegisteredInstruments() {

		return getKeys();
	}

	/**
	 * 
	 * 
	 * @param instrument instrument
	 * @throws Exception Exception
	 */
	public void registerInstrument(String instrument) throws Exception {

		__defaultInstrument = instrument;
		registerDataSeries(instrument);
	}

	/**
	 * Returns the :class:`pyalgotrade.dataseries.bards.BarDataSeries` for a given instrument.
	 * 
	 * @return BarDataSeries
	 */
	public BarDataSeries getDataSeries() {

		String instrument = __defaultInstrument;
		return getDataSeries(instrument);
	}

	/**
	 * Returns the :class:`pyalgotrade.dataseries.bards.BarDataSeries` for a given instrument.
	 * 
	 * @param instrument Instrument identifier. If None, the default instrument is returned.
	 * @return `pyalgotrade.dataseries.bards.BarDataSeries`.
	 */
	public BarDataSeries getDataSeries(String instrument) {

		return (BarDataSeries) __getitem__(instrument);
	}

	@Override
	public String toString() {

		return String.format("BarFeed {instruments: %s (...), freq: %s, currDatetime: %s}", getDefaultInstrument(),
		        getFrequency(), getCurrentDateTime());
	}
}
